﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Net;

namespace Algobox.JuicyLib.Net
{
    public class JuicyFTP
    {
        public JuicyFTP(string inFtpUser, string inFtpPass, string inFtpURI, FTPMode mode, FTPCommand command)
        {
            FTP_PASS = inFtpPass;
            FTP_USER = inFtpUser;

            inFtpURI = inFtpURI.Trim();

            // check for malformed URI
            if (inFtpURI[inFtpURI.Length - 1] != '/')
                FTP_URI = inFtpURI + '/';
            else
                FTP_URI = inFtpURI;

            FTP_URI_BACKUP = String.Empty;

            _mode = mode;
            _command = command;
        }

        public JuicyFTP(string inFtpUser, string inFtpPass, string inFtpURI, string inFtpBackupURI, FTPMode mode, FTPCommand command)
        {
            FTP_PASS = inFtpPass;
            FTP_USER = inFtpUser;

            // check for malformed URI
            if (inFtpURI[inFtpURI.Length - 1] != '/')            
                FTP_URI = inFtpURI + '/';            
            else
                FTP_URI = inFtpURI;

            FTP_URI_BACKUP = inFtpBackupURI;
            
            _mode = mode;
            _command = command;
        }

        readonly string FTP_USER;
        readonly string FTP_PASS;
        readonly string FTP_URI;
        readonly string FTP_URI_BACKUP;

        const string IS_DIR_WIN = "<DIR>";
        const int INDEX_WIN_DIR = 2;
        const int INDEX_WIN_NAME = 3;
        
        const string IS_DIR_NIX = "d";
        const char INDEX_NIX_FILE = '-';
        const int INDEX_NIX_NAME = 8;

        readonly FTPMode _mode;
        readonly FTPCommand _command;

        public enum FTPMode
        {
            Windows,
            Unix
        }

        public enum FTPCommand
        {
            Passive,
            Aggressive
        }

        public static bool IsFTPUri(String uriString)
        {
            Uri uri;
            try
            {
                uri = new Uri(uriString);
            }
            catch
            {
                return false;
            }
            if (uri.Scheme != Uri.UriSchemeFtp)
            {
                return false;
            }
            return true;
        }

        private FtpWebRequest CreateRequest()
        {
            return CreateRequest(string.Empty);
        }

        private FtpWebRequest CreateRequest(string fileName)
        {
            // remove / from filename
            if (fileName == "/")
                fileName = String.Empty;
            else if(fileName.Length > 1 && fileName[0] == '/')
                fileName = fileName.Substring(1);
            
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(FTP_URI + fileName);
            request.Credentials = new NetworkCredential(FTP_USER, FTP_PASS);
            request.UseBinary = true;
            return request;
        }
        
        private FtpWebRequest CreateRequest(Uri uriLocation)
        {
            FtpWebRequest request = (FtpWebRequest)WebRequest.Create(uriLocation);
            request.UseBinary = true;
            request.Credentials = new NetworkCredential(FTP_USER, FTP_PASS);
            return request;
        }



        private byte[] DownloadData(string fileUri)
        {
            WebClient webRequest = new WebClient();

            webRequest.Credentials = new NetworkCredential(FTP_USER, FTP_PASS);

            byte[] newFileData = null;

            try
            {
                newFileData = webRequest.DownloadData(fileUri);
            }
            catch (WebException e)
            {
                Console.WriteLine(e.ToString());
            }

            return newFileData;
        }


        private bool UploadData(string fileUri, byte[] data)
        {
            WebClient webRequest = new WebClient();

            webRequest.Credentials = new NetworkCredential(FTP_USER, FTP_PASS);

            try
            {
                webRequest.UploadData(fileUri, data);
            }
            catch (WebException e)
            {
                Console.WriteLine(e.ToString());
                return false;
            }

            return true;
        }



        //
        // UNIX FTP server
        //
        // drwx------ 1 mailbox group           1024 Jul 31 11:08 archives
        // -rw------- 1 message group           1097 Jul 31 04:40 confirm.txt.1240.asc.09.07.31_04.40

        public bool TryGetFileList(out List<string> fileList)
        {
            fileList = new List<string>();

            StreamReader reader = null;

            string strCurLine;
            string[] arrFileProperties;

            FtpWebRequest request = CreateRequest();

            request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;            

            try
            {
                FtpWebResponse listResponse = (FtpWebResponse)request.GetResponse();
                reader = new StreamReader(listResponse.GetResponseStream());
                strCurLine = reader.ReadLine();

                if (_mode == FTPMode.Windows)
                {                    
                    while (strCurLine != null)
                    {
                        arrFileProperties = strCurLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);

                        if (arrFileProperties[INDEX_WIN_DIR] != IS_DIR_WIN
                            && arrFileProperties[INDEX_WIN_DIR] != IS_DIR_NIX)
                        {
                            fileList.Add(arrFileProperties[INDEX_WIN_NAME]);
                        }
                        strCurLine = reader.ReadLine();
                    }
                }
                // else unix mode
                else
                {
                    while (strCurLine != null)
                    {
                        if (strCurLine[0] == INDEX_NIX_FILE)
                        {
                            arrFileProperties = strCurLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                            fileList.Add(arrFileProperties[INDEX_NIX_NAME]);                            
                        }
                        strCurLine = reader.ReadLine();
                    }
                }
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine(e.ToString());
                System.Diagnostics.Trace.WriteLine("FTP FILE LISTING FAILED");
                Console.WriteLine("FTP FILE LISTING FAILED");
                return false;
            }


            return true;
        }




        public bool TryGetFileListRecursive(string directory, out List<string> fileList)
        {
            fileList = new List<string>();

            StreamReader reader = null;

            string strCurLine;
            string[] arrFileProperties;

            FtpWebRequest request = CreateRequest(directory);

            request.Method = WebRequestMethods.Ftp.ListDirectoryDetails;
            request.UsePassive = _command == FTPCommand.Passive;

            try
            {
                FtpWebResponse listResponse = (FtpWebResponse)request.GetResponse();
                reader = new StreamReader(listResponse.GetResponseStream());
                strCurLine = reader.ReadLine();

                if (_mode == FTPMode.Windows)
                {
                    while (strCurLine != null)
                    {
                        arrFileProperties = strCurLine.Split(new char[] { ' ' }, 4, StringSplitOptions.RemoveEmptyEntries);

                        // recurse into directory
                        if (arrFileProperties[INDEX_WIN_DIR] == IS_DIR_WIN
                            || arrFileProperties[INDEX_WIN_DIR] == IS_DIR_NIX)
                        {
                            List<string> innerFileList;
                            if (FTP_URI + arrFileProperties[INDEX_WIN_NAME] + "/" != FTP_URI_BACKUP)
                            {
                                if (TryGetFileListRecursive(request.RequestUri.LocalPath + arrFileProperties[INDEX_WIN_NAME], out innerFileList))
                                {
                                    if(innerFileList.Count > 0)
                                        fileList.AddRange(innerFileList);
                                }
                            }
                        }
                        else
                        {
                            if (request.RequestUri.LocalPath == "/")
                                fileList.Add("/" + arrFileProperties[INDEX_WIN_NAME]);
                            else
                                fileList.Add(request.RequestUri.LocalPath + "/" + arrFileProperties[INDEX_WIN_NAME]);
                        }
                        strCurLine = reader.ReadLine();
                    }
                }
                // else unix mode
                else
                {
                    while (strCurLine != null)
                    {
                        if (strCurLine[0] == INDEX_NIX_FILE)
                        {
                            arrFileProperties = strCurLine.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
                            fileList.Add(arrFileProperties[INDEX_NIX_NAME]);
                        }
                        strCurLine = reader.ReadLine();
                    }
                }
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine(e.ToString());
                System.Diagnostics.Trace.WriteLine("FTP FILE LISTING FAILED");
                Console.WriteLine("FTP FILE LISTING FAILED");
                return false;
            }


            return true;
        }


        public bool GetFileAsStream(string fileName, out Stream reader)
        {
            FtpWebRequest request = CreateRequest(fileName);
            request.UsePassive = true;
            try
            {
                FtpWebResponse getResponse = (FtpWebResponse)request.GetResponse();
                reader = getResponse.GetResponseStream();
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine(e.ToString());
                System.Diagnostics.Trace.WriteLine("FTP FILE LISTING FAILED");

                reader = null;
                return false;
            }

            return true;
        }


        public bool GetFileAsStreamReader(string fileName, out StreamReader reader)
        {
            System.Diagnostics.Trace.WriteLine("FTP FILE READER STARTING");

            reader = null;

            FtpWebRequest request = CreateRequest(fileName);

            request.UsePassive = true;

            try
            {
                FtpWebResponse getResponse = (FtpWebResponse)request.GetResponse();
                reader = new StreamReader(getResponse.GetResponseStream());
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine(e.ToString());
                System.Diagnostics.Trace.WriteLine("FTP FILE LISTING FAILED");

                reader = null;
                return false;
            }

            return true;
        }


        public bool GetFileAsString(string fileName, out string fileData)
        {
            fileData = String.Empty;
            StreamReader reader;

            if (GetFileAsStreamReader(fileName, out reader))
            {
                try
                {
                    fileData = reader.ReadToEnd();
                    return true;
                }
                catch
                {
                    return false;
                }
            }
            return false;
        }


        public bool GetFileAsString(string fileName, out string fileData, out Encoding encoding)
        {
            fileData = String.Empty;
            encoding = Encoding.Default;
            StreamReader reader;

            if (GetFileAsStreamReader(fileName, out reader))
            {
                try
                {
                    fileData = reader.ReadToEnd();
                    encoding = reader.CurrentEncoding;
                    return true;
                }
                catch
                {
                    return false;
                }
            }
            return false;
        }


        public bool BackupFile(string fileName)
        {
            if (FTP_URI_BACKUP == String.Empty)
            {
                System.Diagnostics.Trace.WriteLine("FTP FILE BACKUP FAILED - NO BACKUP URI");
                Console.WriteLine("FTP FILE BACKUP FAILED - NO BACKUP URI");
                return false;
            }
            else
            {
                string message;
                return BackupFile(fileName, FTP_URI_BACKUP, out message);
            }
        }

        public bool BackupFile(string fileName, string backupDir)
        {
            string message;
            return BackupFile(fileName, FTP_URI_BACKUP, out message);
        }

        public bool BackupFile(string fileName, string backupDir, out string message)
        {
            bool bBackupSuccess = false;


            if (CreateDirectoryRecursive(backupDir))
            {
                Uri backupUri = new Uri(backupDir);

                // load file request
                FtpWebRequest request = CreateRequest(fileName);

                fileName = fileName.Substring(fileName.LastIndexOf('/') + 1);

                try
                {
                    DeleteFile(backupUri.LocalPath + fileName);

                    System.Diagnostics.Debug.WriteLine("FTP Backing up " + backupUri.LocalPath + fileName);

                    request.Method = WebRequestMethods.Ftp.Rename;

                    request.RenameTo = backupUri.LocalPath + fileName;

                    FtpWebResponse response = (FtpWebResponse)request.GetResponse();

                    Stream renameStream = response.GetResponseStream();

                    renameStream.Close();

                    response.Close();

                    bBackupSuccess = true;
                    message = "FTP file backup successful from " + backupUri.LocalPath + fileName + " to " + backupDir + fileName;

                    System.Diagnostics.Trace.WriteLine(message);
                }
                catch (Exception e)
                {
                    message = "FTP file backup failed from " + backupUri.LocalPath + fileName + " to " + backupDir + fileName;
                    System.Diagnostics.Debug.WriteLine(e.ToString());
                    System.Diagnostics.Debug.WriteLine(message);
                }
            }
            else
            {
                message = "FTP file backup failed to recursivly create directory " + backupDir + fileName;
            }
            
            return bBackupSuccess;
        }



        public bool DeleteFile(string fileName)
        {
            System.Diagnostics.Trace.WriteLine("FTP FILE DELETE STARTING");

            try
            {
                FtpWebRequest request = CreateRequest(fileName);

                request.Method = WebRequestMethods.Ftp.DeleteFile;
                request.UsePassive = false;

                FtpWebResponse response = (FtpWebResponse)request.GetResponse();
            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine(e.ToString());
                System.Diagnostics.Trace.WriteLine("FTP FILE DELETE FAILED");
                return false;
            }

            return true;
        }



        public bool CreateDirectoryRecursive(string remoteDirPath)
        {
            try
            {
                Uri remoteUri = new Uri(remoteDirPath);

                FtpWebRequest request = (FtpWebRequest)WebRequest.Create(remoteUri);
                request.Credentials = new NetworkCredential(FTP_USER, FTP_PASS);
                request.Method = WebRequestMethods.Ftp.ListDirectory;
                try
                {
                    using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
                    {
                        return true;
                    }
                }
                catch
                {
                    System.Diagnostics.Debug.WriteLine("Directory not found for " + remoteDirPath);
                    Console.WriteLine("Directory not found for " + remoteDirPath);
                }

                string dir = String.Empty;
                foreach (string dirPart in remoteUri.Segments)
                {
                    if (dirPart == "/")
                        continue;

                    dir += dirPart;

                    request = (FtpWebRequest)WebRequest.Create(FTP_URI + dir);
                    request.Credentials = new NetworkCredential(FTP_USER, FTP_PASS);
                    request.Method = WebRequestMethods.Ftp.ListDirectory;
                    try
                    {
                        using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
                        {
                            // Okay.  
                        }
                    }
                    catch
                    {
                        request = (FtpWebRequest)WebRequest.Create(FTP_URI + dir);
                        request.Credentials = new NetworkCredential(FTP_USER, FTP_PASS);
                        request.Method = WebRequestMethods.Ftp.MakeDirectory;

                        using (var resp = (FtpWebResponse)request.GetResponse())
                        {
                            Console.WriteLine(resp.StatusCode + " " + FTP_URI + dir);
                        }
                    }
                }
                return true;
            }
            catch
            {
                System.Diagnostics.Debug.WriteLine("Failed to create (or check) directories for " + remoteDirPath);
                Console.WriteLine("Failed to create (or check) directories for " + remoteDirPath);
            }
            return false;
        }

    }
}
